package com.suxin.notify;

import com.google.common.collect.Maps;
import com.suxin.notify.listener.SmartSubscriber;
import com.suxin.notify.listener.Subscriber;

import java.util.Iterator;
import java.util.Map;
import java.util.ServiceLoader;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.BiFunction;

/**
 * @author Tang
 * @classname NotifyCenter
 * @description [  ]
 * @date 2022/9/30 14:28
 */
public class NotifyCenter {

    private static int ringBufferSize = 16384;

    private static int shareBufferSize = 1024;

    private static final AtomicBoolean CLOSE = new AtomicBoolean(false);

    private static BiFunction<Class<? extends Event>, Integer, EventPublisher> publisherFactory = null;

    private static final NotifyCenter INSTANCE = new NotifyCenter();

    private DefaultSharePublisher sharePublisher;

    private final Map<String, EventPublisher> publisherMap = Maps.newConcurrentMap();

    private static Class<? extends EventPublisher> clazz = null;


    static {
        String ringBufferSizeProperty = "nacos.core.ring-buffer-size";
        ringBufferSize = Integer.getInteger(ringBufferSizeProperty, 16384);

        String shareBufferSizeProperty = "nacos.core.share-buffer-size";
        shareBufferSize = Integer.getInteger(shareBufferSizeProperty, 1024);

        // 本地的方式进行加载
        ServiceLoader<EventPublisher> loader = ServiceLoader.load(EventPublisher.class);
        Iterator<EventPublisher> iterator = loader.iterator();
        if (iterator.hasNext()) {
            clazz = iterator.next().getClass();
        }else {
            clazz = DefaultPublisher.class;
        }
        // 发布者构建的工厂类
        publisherFactory = new BiFunction<Class<? extends Event>, Integer, EventPublisher>() {
            @Override
            public EventPublisher apply(Class<? extends Event> aClass, Integer bufferSize) {
                try {
                    EventPublisher eventPublisher = clazz.newInstance();
                    // 初始化
                    eventPublisher.init(aClass,bufferSize);
                    return eventPublisher;
                } catch (Exception e) {
                    e.printStackTrace();
                }
                return null;
            }
        };

        INSTANCE.sharePublisher = new DefaultSharePublisher();
        INSTANCE.sharePublisher.init(SlowEvent.class,shareBufferSize);

        Runtime.getRuntime()
                .addShutdownHook(new Thread(() -> {
                    try {
                        shutDown();
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }));
    }

    public static <T> void registerSubscriber(final Subscriber subscriber) {
        if (subscriber instanceof SmartSubscriber) {
            for (Class<? extends Event> subscribeType : ((SmartSubscriber) subscriber).subscribeTypes()) {
                if (subscribeType.isAssignableFrom(SlowEvent.class)) {
                    INSTANCE.sharePublisher.addSubscribe(subscriber, subscribeType);
                }else {
                    addSubscriber(subscriber, subscribeType);
                }
            }
        }
        Class<? extends Event> subscribeType = subscriber.subscribeType();
        if (subscribeType.isAssignableFrom(SlowEvent.class)) {
            INSTANCE.sharePublisher.addSubscribe(subscriber,subscribeType);
            return;
        }
        addSubscriber(subscriber,subscribeType);
    }

    public static boolean publishEvent(Event event) {
        return publishEvent(event.getClass(),event);
    }

    private static boolean publishEvent(Class<? extends Event> subscribeType,Event event) {
        String topic = subscribeType.getCanonicalName();
        EventPublisher eventPublisher = INSTANCE.getPublisherMap().get(topic);
        return eventPublisher.publish(event);
    }

    private static void addSubscriber(Subscriber subscriber,
                                      Class<? extends Event> subscribeType) {
        String topic = subscribeType.getCanonicalName();
        INSTANCE.getPublisherMap().computeIfAbsent(topic, (key) -> {
            return NotifyCenter.publisherFactory.apply(subscribeType, ringBufferSize);
        });
        EventPublisher publisher = INSTANCE.getPublisherMap().get(topic);
        publisher.addSubscribe(subscriber);
    }


    public Map<String, EventPublisher> getPublisherMap() {
        return publisherMap;
    }

    public static void shutDown() throws Exception {
        INSTANCE.sharePublisher.shutdown();
    }

    public DefaultSharePublisher getSharePublisher() {
        return sharePublisher;
    }
}
